#include "Gk.h"

#define D3D_CHECK(s)	{ HRESULT h = s; if (FAILED(h)) { CR_ERROR_STRING("D3D Error"); } }

namespace gk
{
	ContextInternal::ContextInternal()
	: m_pD3DDevice(NULL)
	, m_pCurVertexShader(NULL)
	, m_pCurPixelShader(NULL)
	, m_flushState(true)
	{
		memset(m_samplersUsed, 0, sizeof(m_samplersUsed));
	}

	void ContextInternal::BeginFrame()
	{
		CR_ASSERT(m_pCurVertexShader == 0 && m_pCurPixelShader == 0);

		D3D_CHECK(m_pD3DDevice->BeginScene());

		m_flushState = true;
		if (m_flushState)
		{
			m_currentState.SetDefault();
			CommitStateBlock(m_currentState);
		}
	}

	void ContextInternal::EndFrame()
	{
		CR_ASSERT(m_pCurVertexShader == 0 && m_pCurPixelShader == 0);
		D3D_CHECK(m_pD3DDevice->EndScene());
	}

	void ContextInternal::SetRenderTarget(RenderTargetHandle hRenderTarget)
	{
		if (hRenderTarget.IsValid())
		{
			for (cr::size_t i = 0; i < Constants::MaxColourTargets; ++i)
			{
				LPDIRECT3DSURFACE9 pSurface = 0;
				RenderSurfaceHandle hRenderSurface = hRenderTarget->m_hColourSurface[i];
				if (hRenderSurface.IsValid())
					pSurface = hRenderSurface->m_internal.m_pSurface;

				D3D_CHECK(m_pD3DDevice->SetRenderTarget(i, pSurface));
			}

			LPDIRECT3DSURFACE9 pDepth = 0;
			RenderSurfaceHandle hDepthSurface = hRenderTarget->m_hDepthSurface;
			if (hDepthSurface.IsValid())
				pDepth = hDepthSurface->m_internal.m_pSurface;

			D3D_CHECK(m_pD3DDevice->SetDepthStencilSurface(pDepth));
			m_currentRenderTarget = *hRenderTarget;
		}
		else
		{
			for (cr::size_t i = 0; i < Constants::MaxColourTargets; ++i)
			{
				D3D_CHECK(m_pD3DDevice->SetRenderTarget(i, NULL));
			}
			D3D_CHECK(m_pD3DDevice->SetDepthStencilSurface(NULL));
			cr::ClearObject(m_currentRenderTarget);
		}
	}

	void ContextInternal::ClearRenderTarget(ClearType::Enum clearType, float r, float g, float b, float a, float depth, cr::uint8_t stencil)
	{
		DWORD flags = 0;

		bool hasColourTarget = false;
		for (cr::size_t i = 0; i < Constants::MaxColourTargets && !hasColourTarget; ++i)
			hasColourTarget = m_currentRenderTarget.m_hColourSurface[i].IsValid();
		bool hasDepthTarget = m_currentRenderTarget.m_hDepthSurface.IsValid();
		bool hasStencilTarget = m_currentRenderTarget.m_hStencilSurface.IsValid();

		flags |= (hasColourTarget && (clearType & ClearType::ColourTarget)) ? D3DCLEAR_TARGET : 0;
		flags |= (hasDepthTarget && (clearType & ClearType::DepthTarget)) ? D3DCLEAR_ZBUFFER: 0;
		flags |= (hasStencilTarget && (clearType & ClearType::StencilTarget)) ? D3DCLEAR_STENCIL: 0;
		D3DCOLOR clearColour = D3DCOLOR_ARGB(cr::uint32_t(a*255.0f),cr::uint32_t(r*255.0f),cr::uint32_t(g*255.0f),cr::uint32_t(b*255.0f));
		D3D_CHECK(m_pD3DDevice->Clear(0, NULL, flags, clearColour, depth, stencil));
	}

	void ContextInternal::SetRenderBinding(RenderBindingHandle hRenderBinding)
	{
		if (hRenderBinding.IsValid())
		{
			if (m_hCurRenderBinding.IsValid())
			{
				SetRenderBinding(NULL);
			}

			// Bind streams
			for (cr::size_t iVertexBuffer = 0; iVertexBuffer < Constants::MaxVertexStreamBindings; ++iVertexBuffer)
			{
				BufferHandle hBuffer = hRenderBinding->m_vertexBuffer[iVertexBuffer];
				if (hBuffer.IsValid())
				{
					StreamDescriptionHandle hStreamDescription = hBuffer->m_hStreamDescription;
					CR_ASSERT(hStreamDescription.IsValid());

					D3D_CHECK(m_pD3DDevice->SetVertexDeclaration(hStreamDescription->m_internal.m_pVertexDeclaration));
					D3D_CHECK(m_pD3DDevice->SetStreamSource(iVertexBuffer, hBuffer->m_internal.m_pVertexBuffer, 0, hBuffer->m_stride));
				}
			}

			BeginGpuProgram(hRenderBinding->m_hGpuProgram);

			m_hCurRenderBinding = hRenderBinding;
		}
		else
		{
			if (m_hCurRenderBinding.IsValid())
			{
				EndGpuProgram();
				m_hCurRenderBinding = NULL;
			}
		}
	}

	void ContextInternal::DrawIndexedPrimitives(BufferHandle hIndexBuffer, PrimitiveType::Enum primitiveType, cr::size_t indexCount, cr::size_t startIndex, cr::size_t vertexCount, cr::size_t startVertex)
	{
		BufferInternal& indexBufferInternal = hIndexBuffer->m_internal;
		D3D_CHECK(m_pD3DDevice->SetIndices(indexBufferInternal.m_pIndexBuffer));
		cr::size_t numVertexBufferElements = m_hCurRenderBinding->m_vertexBuffer[0]->m_numElements;
		D3D_CHECK(m_pD3DDevice->DrawIndexedPrimitive( 
			TypeConversion::ConvertPrimitiveType(primitiveType),
			0, 
			(UINT)startVertex, 
			(UINT)(numVertexBufferElements-startVertex), 
			(UINT)startIndex, 
			(UINT)(indexCount / 3)));
	}

	void ContextInternal::DrawPrimitives(PrimitiveType::Enum primitiveType, cr::size_t startVertex, cr::size_t numVertices)
	{
		D3D_CHECK(m_pD3DDevice->DrawPrimitive( 
			TypeConversion::ConvertPrimitiveType(primitiveType),
			(UINT)startVertex, 
			(UINT)(numVertices / 3)));
	}

	void ContextInternal::BeginGpuProgram(GpuProgramHandle hGpuProgram)
	{
		CR_ASSERT(m_pCurVertexShader == 0 && m_pCurPixelShader == 0);

		D3D_CHECK(m_pD3DDevice->SetPixelShader(hGpuProgram->m_internal.m_pPixelShader));
		D3D_CHECK(m_pD3DDevice->SetVertexShader(hGpuProgram->m_internal.m_pVertexShader));

		m_pCurPixelShader = hGpuProgram->m_internal.m_pPixelShader;
		m_pCurVertexShader = hGpuProgram->m_internal.m_pVertexShader;
	}

	void ContextInternal::EndGpuProgram()
	{
		CR_ASSERT(m_pCurVertexShader != 0 && m_pCurPixelShader != 0);
		m_pCurVertexShader = NULL;
		m_pCurPixelShader = NULL;
		m_pD3DDevice->SetPixelShader(NULL);
		m_pD3DDevice->SetVertexShader(NULL);

		// Disable all samplers
		for (cr::size_t i = 0; i < Constants::MaxSamplers; ++i)
			if (m_samplersUsed[i])
			{
				m_pD3DDevice->SetTexture(i, NULL);
				m_samplersUsed[i] = false;
			}
	}

	void ContextInternal::CommitConstantBuffer(ConstantBufferHandle hConstantBuffer)
	{
		switch (hConstantBuffer->m_internal.m_type)
		{
		case shared::prehash::hs_PixelShaderTextures:
		case shared::prehash::hs_VertexShaderTextures:
			{
				for (cr::size_t idxRegister = hConstantBuffer->m_internal.m_startRegister, idxConstant = 0; idxConstant < hConstantBuffer->m_internal.m_registerCount; ++idxConstant, ++idxRegister)
				{
					m_pD3DDevice->SetTexture(idxRegister, hConstantBuffer->m_internal.m_textures[idxConstant].v);
				}
				break;
			}
		case shared::prehash::hs_PixelShaderFloats:
			{
				m_pD3DDevice->SetPixelShaderConstantF(hConstantBuffer->m_internal.m_startRegister, (float*)hConstantBuffer->m_internal.m_floats, hConstantBuffer->m_internal.m_registerCount);
				break;
			}
		case shared::prehash::hs_PixelShaderInts:
			{
				m_pD3DDevice->SetPixelShaderConstantI(hConstantBuffer->m_internal.m_startRegister, (int*)hConstantBuffer->m_internal.m_ints, hConstantBuffer->m_internal.m_registerCount);
				break;
			}
		case shared::prehash::hs_PixelShaderBools:
			{
				m_pD3DDevice->SetPixelShaderConstantB(hConstantBuffer->m_internal.m_startRegister, (BOOL*)hConstantBuffer->m_internal.m_bools, hConstantBuffer->m_internal.m_registerCount);
				break;
			}
		case shared::prehash::hs_VertexShaderFloats:
			{
				m_pD3DDevice->SetVertexShaderConstantF(hConstantBuffer->m_internal.m_startRegister, (float*)hConstantBuffer->m_internal.m_floats, hConstantBuffer->m_internal.m_registerCount);
				break;
			}
		case shared::prehash::hs_VertexShaderInts:
			{
				m_pD3DDevice->SetVertexShaderConstantI(hConstantBuffer->m_internal.m_startRegister, (int*)hConstantBuffer->m_internal.m_ints, hConstantBuffer->m_internal.m_registerCount);
				break;
			}
		case shared::prehash::hs_VertexShaderBools:
			{
				m_pD3DDevice->SetVertexShaderConstantB(hConstantBuffer->m_internal.m_startRegister, (BOOL*)hConstantBuffer->m_internal.m_bools, hConstantBuffer->m_internal.m_registerCount);
				break;
			}
		}
	}

	void ContextInternal::CommitGpuProgram()
	{
	}

	void ContextInternal::CommitStateBlock(StateBlockHandle hStateBlock)
	{
		for (cr::size_t idxRenderState = RenderState::First; idxRenderState < RenderState::NumberOf; ++idxRenderState)
		{
			StateBlock::StateValue& stateValue = hStateBlock->m_renderStateValues[idxRenderState];

			if (m_flushState || m_currentState.m_renderStateValues[idxRenderState] != stateValue)
			{
				D3DRENDERSTATETYPE renderState;
				DWORD value;
				TypeConversion::ConvertRenderState((RenderState::Enum)idxRenderState, stateValue, renderState, value);
				D3D_CHECK(m_pD3DDevice->SetRenderState(renderState, value));
				m_currentState.m_renderStateValues[idxRenderState] = stateValue;
			}
		}

		for (cr::size_t idxSamplerIndex = SamplerIndex::First; idxSamplerIndex < SamplerIndex::NumberOf; ++idxSamplerIndex)
		{
			for (cr::size_t idxSamplerState = SamplerState::First; idxSamplerState < SamplerState::NumberOf; ++idxSamplerState)
			{
				StateBlock::StateValue& stateValue = hStateBlock->m_samplerStateValues[idxSamplerIndex][idxSamplerState];

				if (m_flushState || m_currentState.m_samplerStateValues[idxSamplerIndex][idxSamplerState] != stateValue)
				{
					D3DSAMPLERSTATETYPE samplerState;
					DWORD value = 0;
					TypeConversion::ConvertSamplerState((SamplerState::Enum)idxSamplerState, stateValue, samplerState, value);
					D3D_CHECK(m_pD3DDevice->SetSamplerState(idxSamplerIndex, samplerState, value));
					m_currentState.m_samplerStateValues[idxSamplerIndex][idxSamplerState].m_samplerStateValue = stateValue.m_samplerStateValue;
				}
			}
		}

		m_flushState = false;
	}
}